package com.shuyuwl.core.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.shuyuwl.core.enums.ResultEnum;
import com.shuyuwl.core.exception.SymServiceException;
import com.shuyuwl.core.utils.annotation.CopyIgnore;
import com.shuyuwl.core.utils.annotation.Name;
import org.thymeleaf.util.ArrayUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 *VO转换
 * @author xpk
 */
public class VoUtil {

    private static final String PROXY = "proxy";
    /**
     * 将前台传入的json 复制给entity
     * @param object
     * @param targetVOClass
     * @param target
     * @param <E>
     * @param <V>
     */
    public  static <E,V> void copyToEntity(JSONObject object,Class<V> targetVOClass,E target){
        /*将json中的key转换为VO中注解的key若无注解，则用原名*/
        Map<String,Field> VoFields = getFieldsByModelClass(targetVOClass);
        for (Map.Entry<String,Field> entry : VoFields.entrySet()) {
            if (object.containsKey(entry.getKey())) {
                Name name = entry.getValue().getAnnotation(Name.class);
                if (null != name) {
                    object.put(name.value(), object.get(entry.getKey()));
                    object.remove(entry.getKey());
                }
            }
        }

        Class model =  target.getClass();

        if(model.getClasses() != null && model.getClasses().toString().contains(PROXY)) {
            invokeGet(object, model, target, model.getDeclaringClass());
        } else {
            invokeGet(object, model, target);
        }
    }

    public static <E,V> void copyToEntity(V source,E target) {
        Map<String,Field> sFields = getFields(source);

        Map<String,Field> eFields = getFields(target);

        sFields.forEach((key,value) -> {
            String temp = key;
            if (value.getAnnotation(Name.class) != null) {
                temp = value.getAnnotation(Name.class).value();
            }
            String finalTemp = temp;
            eFields.forEach((eKey, eValue)->{
               if (finalTemp.equals(eKey)) {
                   try {
                       Method get = getMethodForGet(source.getClass(), key);

                       Method set = getMethodForSet(target.getClass(), finalTemp, eValue.getType());

                       if (get.invoke(source) != null) {
                           set.invoke(target, JSON.parseObject(JSON.toJSONString(get.invoke(source)),eValue.getType()));
                       }
                   } catch (NoSuchMethodException e) {
                       throw new SymServiceException(ResultEnum.UNKOWN_ERROR);
                   } catch (IllegalAccessException e) {
                       throw new SymServiceException(ResultEnum.UNKOWN_ERROR);
                   } catch (InvocationTargetException e) {
                       throw new SymServiceException(ResultEnum.UNKOWN_ERROR);
                   }
               }
            });
        });

    }

   /**
     * 将entity转换为相应的VO
     * @param entity
     * @param targetClass
     * @param <E>
     * @param <V>
     * @return
    */
    public static <E,V> V createVO(E entity, Class<V> targetClass) {
        try {
            V target = targetClass.getDeclaredConstructor().newInstance();

            Map<String,Field> VoFields = getFieldsByModelClass(targetClass);
            Map<String,Field> eFields = getFields(entity);
            for (Map.Entry<String,Field> entry: VoFields.entrySet()) {
                Name name = entry.getValue().getAnnotation(Name.class);
                Method get = null;
                Method set = null;
                for (String key: eFields.keySet()) {
                    CopyIgnore copyIgnore = entry.getValue().getAnnotation(CopyIgnore.class);
                    if (null != copyIgnore) {
                        break;
                    }
                    if(null != name) {
                        /*存在注解时*/
                        if (name.value().equals(eFields.get(key).getName())) {
                            get = getMethodForGet(entity.getClass(),name.value());
                        }
                    } else {
                        /*不存在注解时*/
                        if (entry.getValue().getName().equals(eFields.get(key).getName())) {
                            /*字段名称相同时*/
                            get = getMethodForGet(entity.getClass(),entry.getValue().getName());
                            set = getMethodForSet(targetClass, entry.getValue().getName(), entry.getValue().getType());
                            break;
                        }
                    }
                }
                if (null != set && null != get) {
                    set.invoke(target, JSON.parseObject(JSON.toJSONString(get.invoke(entity)),entry.getValue().getType()));
                }
            }
            return target;
        } catch (InstantiationException e) {
            throw new RuntimeException(e.getMessage());
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e.getMessage());
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e.getMessage());
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e.getMessage());
        }
    }


    private static final Method getMethodForGet(Class<?> target,String name) throws NoSuchMethodException {
        return target.getMethod("get" + name.substring(0,1).toUpperCase() + name.substring(1));
    }

    private static final Method getMethodForSet(Class<?> target,String name,Class<?> type) throws NoSuchMethodException {
        return target.getMethod("set" + name.substring(0,1).toUpperCase() + name.substring(1),
                type);
    }

    private static final<E> void invokeGet(JSONObject object,Class model,E target,Class ...supperClass) {
        Class tempClass = model;
        if (!ArrayUtils.isEmpty(supperClass)) {
            tempClass = supperClass[0];
        }
        Map<String,Field> fields = getFieldsByModelClass(tempClass);

        object.entrySet().forEach(entry->{
            try {
                Method method = getMethodForSet(model, entry.getKey(), fields.get(entry.getKey()).getType());
                method.invoke(target, JSON.parseObject(JSON.toJSONString(entry.getValue()), fields.get(entry.getKey()).getType()));

            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException(e.getMessage());
            }

        });
    }

    private static final Map<String,Field> getFieldsByModelClass(Class model){
        Map<String,Field> fields = new HashMap<>(0);
        while (model != null) {
            for (Field field : model.getDeclaredFields()) {
                fields.put(field.getName(), field);
            }
            model = model.getSuperclass();
        }

        return fields;
    }

    public static final <T> Map<String,Field> getFields(T val) {
        Map<String,Field> fields = new HashMap<>(0);
        if (val.getClass() != null && val.getClass().toString().contains(PROXY)) {
            fields.putAll(getFieldsByModelClass(val.getClass().getDeclaringClass()));
        } else {
            fields.putAll(getFieldsByModelClass(val.getClass()));
        }

        return fields;
    }
}
